#include<iostream>
#include<vector>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
#include<cstdio>
#include"LockGuard.hpp"
using namespace std;
#define NUM 5

//正式学习锁--互斥锁mutex

//我们推荐把锁申明在全局上，这样每个线程都可以访问到：
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//锁的数据类型是pthread_mutex_t 
//声明锁的时候加上PTHREAD_MUTEX_INITIALIZER，这把锁就会自动初始化并且自动释放


//我们对抢票的过程加锁：

//加锁要注意：尽量让持有锁进行的操作简短一些，因为当一个线程持有锁执行操作的时候，其他线程都在同步等待，这会降低线程的并发度

//要使用多线程就要把多个线程对象组织起来，所以我们要先描述，也就先封装线程的对象
class threadData
{
public:
    threadData(int number)
    {
        threadname_="thread-"+to_string(number);
        
    }
public:
    string threadname_;
    
};

//设置票的总量：
int tickets=1000;

//使用多线程模拟多个抢票：

//加锁之后：在得到锁到释放锁的过程中，这一段代码我们可以看作时原子的，因为在执行这段代码的时候，其他线程是无法打断的
void* getTickets(void *args)
{
    threadData *td=static_cast<threadData*>(args);
    const char* name=td->threadname_.c_str();//string转化为const char*类型要使用c_str()函数
    while(true)
    {
        //注意：线程对锁的竞争能力可能会不同
        pthread_mutex_lock(&lock);//锁只有一个，只有申请锁成功的线程才能向下执行，如果申请不成功就会阻塞
        if(tickets>0)
        {
            usleep(1000);
            printf("who=%s,get a ticket:%d\n",name,tickets);
            tickets--;
            pthread_mutex_unlock(&lock);//解锁，归还锁资源，这样线程又可以重新开始竞争锁
        }
        else
        {
            //当有线程抢到锁之后发现票已经没有了，此时break的话就造成锁资源的未释放，导致剩下没有结束的线程会一直在阻塞等待锁资源，从而造成线程残留
            //所以我们要注意，break之前也要释放一下锁资源：
            pthread_mutex_unlock(&lock);
            break;
        }
        //但是此时会触发一个新的问题，线程对锁的竞争能力是不同的，第一个持有锁的线程它刚解锁可能就直接去申请锁资源成功了，所以会导致一个线程一直独占锁的情况
        //此时我们思考一个问题，在现实情况中，我们抢到一张票之后会立刻去抢下一张票吗？当然不会，最起码我们还要把这个票进行一些处理，比如把这个票进行支付啊，进行对身份信息的绑定啊之类的操作，这些操作都会占用时间
        //所以，解决方法就是：抢到票解锁之后我们模拟一下还要对票进行处理，要花费一些时间，此时这个线程就会进行处理操作，从而其他线程就有机会在这个时候竞争到这把锁
        usleep(13);//休眠一小会模拟对票进行处理的过程，此时其他进程就会竞争到该进程刚释放的锁
    }
    printf("%s,quit\n",name);
    return nullptr;
}
void test1()
{
    //对锁进行初始化的方法1:手动初始化锁，并且最后要手动释放锁     方法2：直接自动初始化并且帮我们自动释放锁，只需在声明锁的时候加上=PTHREAD_MUTEX_INITIALIZER
    //pthread_mutex_init(&lock,nullptr);

    vector<pthread_t>tids;
    vector<threadData*>thread_datas;
    //创建多线程：
    for(int i=1;i<=NUM ;i++)//会创建出NUM个线程去执行抢票的任务
    {
        pthread_t tid;
        threadData*td=new threadData(i);
        thread_datas.push_back(td);//线程的信息放在动态数组中管理起来
        pthread_create(&tid,nullptr,getTickets,thread_datas[i-1]);
        tids.push_back(tid);//把tid放在动态数组中管理起来
    }
    for(auto e:tids)//释放多线程
    {
        pthread_join(e,nullptr);
    }
    //释放申请的空间：
    for(auto e:thread_datas)
    {
        delete e;
    }

    //使用完锁要释放锁：
   //pthread_mutex_destroy(&lock);
}

//补充：如果想要把所有的抢票信息输出到一个文件中，只需要./tickets>log.txt（执行可执行程序的时候重定向输出到文件中即可）

//---------------------------------------------以上是加锁的第一种版本，全局锁的版本------------------------------------

//第二种加锁的版本：把全局锁的地址放在对象中传递，因为所有的线程的信息都放在了threadData中，我们可以认为锁也是线程的一种信息，也可以把线程的锁放在这个对象中：
class threadData2
{
public:
    threadData2(int number,pthread_mutex_t *mutex)
    {
        threadname_="thread-"+to_string(number);
        lock=mutex;
    }
public:
    string threadname_;
    pthread_mutex_t *lock;//存放锁的地址，从而每个对象保存的其实都是同一把锁，所有对象的锁地址都指向同一个
};

void* getTickets2(void *args)
{
    threadData2 *td=static_cast<threadData2*>(args);//所有的线程都可以通过td访问到同一个锁资源的地址
    const char* name=td->threadname_.c_str();//string转化为const char*类型要使用c_str()函数
    while(true)
    {
        pthread_mutex_lock(td->lock);//lock指针变量就是锁的地址
        if(tickets>0)
        {
            usleep(1000);
            printf("who=%s,get a ticket:%d\n",name,tickets);
            tickets--;
            pthread_mutex_unlock(td->lock);//解锁，归还锁资源，这样线程又可以重新开始竞争锁
        }
        else
        {
            pthread_mutex_unlock(td->lock);
            break;
        }
        usleep(13);//休眠一小会模拟对票进行处理的过程，此时其他进程就会竞争到该进程刚释放的锁
    }
    printf("%s,quit\n",name);
    return nullptr;
}

void test2()
{
    vector<pthread_t>tids;
    vector<threadData2*>thread_datas;
    //创建多线程：
    for(int i=1;i<=NUM ;i++)//会创建出NUM个线程去执行抢票的任务
    {
        pthread_t tid;
        threadData2*td=new threadData2(i,&lock);//每个对象都保存了这把全局锁的地址
        thread_datas.push_back(td);//线程的信息放在动态数组中管理起来
        pthread_create(&tid,nullptr,getTickets2,thread_datas[i-1]);
        tids.push_back(tid);//把tid放在动态数组中管理起来
    }
    for(auto e:tids)//释放多线程
    {
        pthread_join(e,nullptr);
    }
    //释放申请的空间：
    for(auto e:thread_datas)
    {
        delete e;
    }
}
//这种放置在对象中的锁与第一种锁的使用方式大致相同，只是使用的形式改变了而已

//-----------------------------------------第三种方式：创建临时的LockGuard对象，RAII风格的锁-----------------------------
//这种方式就是要自己完成对锁的封装，封装在LockGuard.hpp中，这样以后使用锁的时候会很方便，把申请锁和释放锁的操作大大简化
//和日志log一样，这里的锁也可以相当于是我们写的一个小插件

void* getTickets3(void *args)
{
    threadData *td=static_cast<threadData*>(args);
    const char* name=td->threadname_.c_str();//string转化为const char*类型要使用c_str()函数
    while(true)
    {
        {   //使用{}就可以把lockguard的声明周期控制在该代码段中，把申请锁和释放锁的操作大大简化

            LockGuard lockguard(&lock);//对象一创建就是在申请锁，没申请成功的线程就会阻塞在这个创建对象中
            if(tickets>0)
            {
                usleep(1000);
                printf("who=%s,get a ticket:%d\n",name,tickets);
                tickets--;
                
            }
            else
            {
                break;//break跳出循环时，也算离开该代码段了，也会调用lockguard的析构函数释放锁资源，防止忘记解锁
            }
        }//一旦退出该代码段就会调用lockguard的析构函数，就会释放锁    
        
        usleep(13);//休眠一小会模拟对票进行处理的过程，此时其他进程就会竞争到该进程刚释放的锁
    }
    printf("%s,quit\n",name);
    return nullptr;
}

void test3()
{
    vector<pthread_t>tids;
    vector<threadData*>thread_datas;
    //创建多线程：
    for(int i=1;i<=NUM ;i++)//会创建出NUM个线程去执行抢票的任务
    {
        pthread_t tid;
        threadData*td=new threadData(i);
        thread_datas.push_back(td);//线程的信息放在动态数组中管理起来
        pthread_create(&tid,nullptr,getTickets,thread_datas[i-1]);
        tids.push_back(tid);//把tid放在动态数组中管理起来
    }
    for(auto e:tids)//释放多线程
    {
        pthread_join(e,nullptr);
    }
    //释放申请的空间：
    for(auto e:thread_datas)
    {
        delete e;
    }
}

int main()
{
    test3();
    return 0;
}

